package com.navinfo.navigation.map;

import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import androidx.core.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import org.locationtech.jts.util.Stopwatch;
import org.oscim.android.MapView;
import org.oscim.android.canvas.AndroidGraphics;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.core.GeoPoint;
import org.oscim.core.MapPosition;
import org.oscim.event.Gesture;
import org.oscim.event.GestureListener;
import org.oscim.event.MotionEvent;
import org.oscim.layers.Layer;
import org.oscim.layers.vector.PathLayer;
import org.oscim.layers.marker.ItemizedLayer;
import org.oscim.layers.marker.MarkerItem;
import org.oscim.layers.marker.MarkerSymbol;
import org.oscim.layers.vector.geometries.Style;
import org.oscim.map.Layers;

import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.ResponsePath;
import com.graphhopper.api.GraphHopperWeb;
import com.graphhopper.util.Parameters.Algorithms;
import com.graphhopper.util.PointList;

import com.navinfo.navigation.model.listeners.MapHandlerListener;
import com.navinfo.navigation.navigator.NaviEngine;
import com.navinfo.util.TargetDirComputer;
import com.xiaoxiao.cateye.library.R;


public class MapHandler
{
  private static MapHandler mapHandler;
  private volatile boolean prepareInProgress = false;
  private volatile boolean calcPathActive = false;
  private MapPosition tmpPos = new MapPosition();
  private GeoPoint startMarker;
  private GeoPoint endMarker;
  private boolean needLocation = false;
  private MapView mapView;
  private ItemizedLayer itemizedLayer;
  private ItemizedLayer customLayer;
  private PathLayer pathLayer;
  private PathLayer polylineTrack;
//  private GraphHopper hopper;
  private MapHandlerListener mapHandlerListener;
  private ImageView naviCenterBtn;
  private PointList trackingPointList = new PointList();
  private int customIcon = R.drawable.ic_my_location_dark_24dp;
//  private MapFileTileSource tileSource;
  /**
   * need to know if path calculating status change; this will trigger MapActions function
   */
  private boolean needPathCal;
  
  public static MapHandler getMapHandler()
  {
    if (mapHandler == null)
    {
      reset();
    }
    return mapHandler;
  }

   /**
    * reset class, build a new instance
    */
  public static void reset()
  {
    mapHandler = new MapHandler();
  }

  private MapHandler()
  {
    setCalculatePath(false,false);
    startMarker = null;
    endMarker = null;
    needLocation = false;
    needPathCal = false;
  }

  public void init(MapView mapView, Activity activity)
  {
    this.mapView = mapView;
    initLayer(activity);
  }
  
  public void initLayer(Activity activity) {
      logUser(activity, "MapHandler:initLayer");
      mapView.map().layers().add(new MapEventsReceiver(mapView.map()));
      naviCenterBtn = activity.findViewById(R.id.img_navi_center);
      // Markers layer
      itemizedLayer = new ItemizedLayer(mapView.map(), (MarkerSymbol) null);
      mapView.map().layers().add(itemizedLayer);
      customLayer = new ItemizedLayer(mapView.map(), (MarkerSymbol) null);
      mapView.map().layers().add(customLayer);
  }


  /**
   * center the LatLong point in the map and zoom map to zoomLevel
   *
   * @param latLong
   * @param zoomLevel (if 0 use current zoomlevel)
   */
  public void centerPointOnMap(GeoPoint latLong, int zoomLevel, float bearing, float tilt)
  {
    if (zoomLevel == 0)
    {
      zoomLevel = mapView.map().getMapPosition().zoomLevel;
    }
    double scale = 1 << zoomLevel;
    tmpPos.setPosition(latLong);
    tmpPos.setScale(scale);
    tmpPos.setBearing(bearing);
    tmpPos.setTilt(tilt);
    mapView.map().animator().animateTo(300, tmpPos);
  }
  
  public void resetTilt(float tilt)
  {
      mapView.map().setMapPosition(mapView.map().getMapPosition().setTilt(tilt));
  }

  /**
   * @return
   */
  public boolean isNeedLocation()
  {
    return needLocation;
  }

  /**
   * set in need a location from screen point (touch)
   *
   * @param needLocation
   */
  public void setNeedLocation(boolean needLocation)
  {
    this.needLocation = needLocation;
  }

  /** Set start or end Point-Marker.
   *  @param p The Point to set, or null.
   *  @param isStart True for startpoint false for endpoint.
   *  @param recalculate True to calculate path, when booth points are set.
   *  @return Whether the path will be recalculated. **/
  public boolean setStartEndPoint(Activity activity, GeoPoint p, boolean isStart, boolean recalculate)
  {
    boolean result = false;
    boolean refreshBoth = false;
    if (startMarker!=null && endMarker!=null && p!=null) { refreshBoth = true; }
      
    if (isStart)
    {
      startMarker = p;
    }
    else { endMarker = p; }

    // remove routing layers
    if ((startMarker==null || endMarker==null) || refreshBoth)
    {
      if (pathLayer!=null) { pathLayer.clearPath(); }
      itemizedLayer.removeAllItems();
    }
    if (startMarker!=null)
    {
      itemizedLayer.addItem(createMarkerItem(activity, startMarker, R.drawable.ic_location_start_24dp, 0.5f, 1.0f));
    }
    if (endMarker!=null)
    {
      itemizedLayer.addItem(createMarkerItem(activity, endMarker, R.drawable.ic_location_end_24dp, 0.5f, 1.0f));
    }
    if (startMarker!=null && endMarker!=null && recalculate)
    {
      recalcPath(activity);
      result = true;
    }
    mapView.map().updateMap(true);
    return result;
  }
  
  public void recalcPath(Activity activity)
  {
    setCalculatePath(true, true);
    calcPath(startMarker.getLatitude(), startMarker.getLongitude(), endMarker.getLatitude(), endMarker.getLongitude(), activity);
  }

  /** Set the custom Point for current location, or null to delete.
   *  Sets the offset to center. **/
  public void setCustomPoint(Activity activity, GeoPoint p)
  {
    if (customLayer==null) { return; } // Not loaded yet.
    customLayer.removeAllItems();
    if (p!=null)
    {
      customLayer.addItem(createMarkerItem(activity, p,customIcon, 0.5f, 0.5f));
      mapView.map().updateMap(true);
    }
  }
  
  public void setCustomPointIcon(Context appContext, int customIcon)
  {
    this.customIcon = customIcon;
    if (customLayer.getItemList().size() > 0)
    { // RefreshIcon
      MarkerItem curSymbol = (MarkerItem) customLayer.getItemList().get(0);
      MarkerSymbol marker = createMarkerItem(appContext, new GeoPoint(0,0), customIcon, 0.5f, 0.5f).getMarker();
      curSymbol.setMarker(marker);
    }
  }

  private MarkerItem createMarkerItem(Context appContext, GeoPoint p, int resource, float offsetX, float offsetY) {
//      Drawable drawable = activity.getDrawable(resource); // Since API21
      Drawable drawable = ContextCompat.getDrawable(appContext, resource);
      Bitmap bitmap = AndroidGraphics.drawableToBitmap(drawable);
      MarkerSymbol markerSymbol = new MarkerSymbol(bitmap, offsetX, offsetY);
      MarkerItem markerItem = new MarkerItem("", "", p);
      markerItem.setMarker(markerSymbol);
      return markerItem;
  }

    /**
     * @return true if already loaded
     */
    boolean isReady() {
      return !prepareInProgress;
    }

    /**
     * start tracking : reset polylineTrack & trackingPointList & remove polylineTrack if exist
     */
    public void startTrack(Activity activity) {
        if (polylineTrack != null) {
            removeLayer(mapView.map().layers(), polylineTrack);
        }
        polylineTrack = null;
        trackingPointList.clear();
        if (polylineTrack != null) { polylineTrack.clearPath(); }
        polylineTrack = updatePathLayer(activity, polylineTrack, trackingPointList, 0x99003399, 4);
        NaviEngine.getNaviEngine().startDebugSimulator(activity);
    }

    /**
     * add a tracking point
     *
     * @param point
     */
    public void addTrackPoint(Activity activity, GeoPoint point) {
      trackingPointList.add(point.getLatitude(), point.getLongitude());
      updatePathLayer(activity, polylineTrack, trackingPointList, 0x9900cc33, 4);
      mapView.map().updateMap(true);
    }
    
  /**
   * remove a layer from map layers
   *
   * @param layers
   * @param layer
   */
  public static void removeLayer(Layers layers, Layer layer)
  {
    if (layers != null && layer != null && layers.contains(layer))
    {
      layers.remove(layer);
    }
  }

    public boolean isCalculatingPath() {
        return calcPathActive;
    }

    private void setCalculatePath(boolean calcPathActive, boolean callListener) {
        this.calcPathActive = calcPathActive;
        if (mapHandlerListener != null && needPathCal && callListener) mapHandlerListener.pathCalculating(calcPathActive);
    }

    public void setNeedPathCal(boolean needPathCal) {
        this.needPathCal = needPathCal;
    }


    /**
     * only tell on object
     *
     * @param mapHandlerListener
     */
    public void setMapHandlerListener(MapHandlerListener mapHandlerListener) {
        this.mapHandlerListener = mapHandlerListener;
    }

    // 计算路径
    public void calcPath(final double fromLat, final double fromLon,
                         final double toLat, final double toLon, final Activity activity) {
        setCalculatePath(true, false);
        log("calculating path ...");
        new AsyncTask<Void, Void, GHResponse>() {
            float time;

            @Override
            protected GHResponse doInBackground(Void... v) {
                Stopwatch sw = new Stopwatch();
                sw.start();
              GHRequest req = new GHRequest(fromLat, fromLon, toLat, toLon).
                      setAlgorithm(Algorithms.DIJKSTRA_BI).setLocale("zh_CN");
                GraphHopperWeb graphHopperWeb = new GraphHopperWeb("http://172.21.91.90:8989/route");
                graphHopperWeb.setPostRequest(false);
//              req.getHints().put(Routing.INSTRUCTIONS, Variable.getVariable().getDirectionsON());
//              req.setVehicle(Variable.getVariable().getTravelMode().toString().toLowerCase());
//              req.setWeighting(Variable.getVariable().getWeighting());
//              if (Variable.getVariable().isShowingSpeedLimits() || Variable.getVariable().isSpeakingSpeedLimits())
//              {
//                  req.getPathDetails().add(com.graphhopper.routing.profiles.MaxSpeed.KEY);
//                  req.getPathDetails().add(com.graphhopper.util.Parameters.Details.AVERAGE_SPEED);
//              }
              GHResponse resp = null;
              if (graphHopperWeb != null)
              {
                resp = graphHopperWeb.route(req);
              }
              if (resp==null || resp.hasErrors())
              {
                NaviEngine.getNaviEngine().setDirectTargetDir(true);
                Throwable error;
                if (resp != null) { error = resp.getErrors().get(0); }
                else { error = new NullPointerException("Hopper is null!!!"); }
                log("Multible errors, first: " + error);
                resp = TargetDirComputer.getInstance().createTargetdirResponse(fromLat, fromLon, toLat, toLon);
              }
              else
              {
                NaviEngine.getNaviEngine().setDirectTargetDir(false);
              }
              time = sw.stop();
              return resp;
            }


            @Override
            protected void onPostExecute(GHResponse ghResp) {
                if (!ghResp.hasErrors()) {
                    ResponsePath resp = ghResp.getBest();
                    log("from:" + fromLat + "," + fromLon + " to:" + toLat + ","
                            + toLon + " found path with distance:" + resp.getDistance()
                            / 1000f + ", nodes:" + resp.getPoints().getSize() + ", time:"
                            + time + " " + resp.getDebugInfo());
                    logUser(activity, "the route is " + (int) (resp.getDistance() / 100) / 10f
                            + "km long, time:" + resp.getTime() / 60000f + "min.");

                    int sWidth = 4;
                    pathLayer = updatePathLayer(activity, pathLayer, resp.getPoints(), 0x9900cc33, sWidth);
                    mapView.map().updateMap(true);
                    Navigator.getNavigator().setGhResponse(resp);
                } else {
                    logUser(activity, "Multible errors: " + ghResp.getErrors().size());
                    log("Multible errors, first: " + ghResp.getErrors().get(0));
                }
                if (NaviEngine.getNaviEngine().isNavigating())
                {
                  setCalculatePath(false, false);
                }
                else
                {
                  setCalculatePath(false, true);
//                  try
//                  {
//                    activity.findViewById(R.id.map_nav_settings_path_finding).setVisibility(View.GONE);
//                    activity.findViewById(R.id.nav_settings_layout).setVisibility(View.VISIBLE);
//                  }
//                  catch (Exception e) { e.printStackTrace(); }
                }
            }
        }.execute();
    }
    
  private PathLayer updatePathLayer(Activity activity, PathLayer ref, PointList pointList, int color, int strokeWidth) {
      if (ref==null) {
          ref = createPathLayer(activity, color, strokeWidth);
          mapView.map().layers().add(ref);
      }
      List<GeoPoint> geoPoints = new ArrayList<>();
      //TODO: Search for a more efficient way
      for (int i = 0; i < pointList.getSize(); i++)
          geoPoints.add(new GeoPoint(pointList.getLatitude(i), pointList.getLongitude(i)));
      ref.setPoints(geoPoints);
      return ref;
  }
  
  public void joinPathLayerToPos(double lat, double lon)
  {
    try
    {
      List<GeoPoint> geoPoints = new ArrayList<>();
      geoPoints.add(new GeoPoint(lat,lon));
      geoPoints.add(pathLayer.getPoints().get(1));
      pathLayer.setPoints(geoPoints);
    }
    catch (Exception e) { log("Error: " + e); }
  }
    
  private PathLayer createPathLayer(Activity activity, int color, int strokeWidth)
  {
      Style style = Style.builder()
        .fixed(true)
        .generalization(Style.GENERALIZATION_SMALL)
        .strokeColor(color)
        .strokeWidth(strokeWidth * activity.getResources().getDisplayMetrics().density)
        .build();
      PathLayer newPathLayer = new PathLayer(mapView.map(), style);
      return newPathLayer;
  }

  public void showNaviCenterBtn(boolean visible)
  {
    if (visible)
    {
      naviCenterBtn.setVisibility(View.VISIBLE);
    }
    else
    {
      naviCenterBtn.setVisibility(View.INVISIBLE);
    }
  }

//  public void setNaviCenterBtn(final FloatingActionButton naviCenterBtn)
//  {
//    this.naviCenterBtn = naviCenterBtn;
//  }
    
  class MapEventsReceiver extends Layer implements GestureListener
  {

      MapEventsReceiver(org.oscim.map.Map map) {
          super(map);
      }

      @Override
      public boolean onGesture(Gesture g, MotionEvent e) {
          if (g instanceof Gesture.Tap) {
              GeoPoint p = mMap.viewport().fromScreenPoint(e.getX(), e.getY());
              if (mapHandlerListener!=null && needLocation)
              {
                mapHandlerListener.onPressLocation(p);
              }
          }
          return false;
      }
  }
  
  private void logUser(Activity activity, String str)
  {
    log(str);
    try
    {
      Toast.makeText(activity, str, Toast.LENGTH_LONG).show();
    }
    catch (Exception e) { e.printStackTrace(); }
  }
  
  private void log(String str)
  {
    Log.i(MapHandler.class.getName(), str);
  }
}

